package edu.gmu.client;

import calendar.CalendarMgrInt;
import calendar.CalendarObj;
import calendar.User;
import calendar.messages.ListRequest;
import calendar.messages.ListResponse;
import calendar.messages.Result;
import edu.gmu.messages.EndPoint;
import edu.gmu.messages.Host;
import edu.gmu.messages.ListManagerRequest;
import edu.gmu.messages.ListManagerResponse;
import edu.gmu.messages.ResourceResponse;
import edu.gmu.messages.ResourceRequest;
import edu.gmu.messages.Topic;
import edu.gmu.middleware.Message;
import edu.gmu.middleware.RemoteStub;
import edu.gmu.naming.NamingService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ClientFrontEnd implements CalendarMgrInt
{
    private List<EndPoint> nameServers = new ArrayList<EndPoint>(); 
    
    /**
     * Creates a new ClientFrontEnd - have to call connect in order to start.
     * @param nameServer
     * @param nameServerPort
     * @throws java.io.IOException
     */
    public ClientFrontEnd( String nameServer, int nameServerPort ) throws IOException
    {
        EndPoint primaryNameServer = new EndPoint(nameServer, nameServerPort);
        this.nameServers.add( primaryNameServer );
    }
    
    /**
     * Utility to create RemoteStub from Endpoint.
     * @param Endpoint
     * @return RemoteStub or null is a problem occurs
     */
    private RemoteStub getRemoteStub( EndPoint endPoint )
    {
        RemoteStub remoteStub = null;
        try
        {
            remoteStub = new RemoteStub(    endPoint.address,
                                            endPoint.port,
                                            endPoint.port,
                                            endPoint.address );
        }
        catch(IOException ioe)
        {
            // ignore
        }
        return remoteStub;
    }
    
    /**
     * Utility to connect to nameServer - new connection each time
     * @retur nnameServerStub
     */
    public RemoteStub getNamingServiceStub() throws IOException
    {
        RemoteStub nameServerStub = null;
        for (int i = 0; i < nameServers.size(); i++)
        {
            EndPoint endPoint = nameServers.get(i);
            
            /*
             * First one that responds - connect and get list of managers
             */
            try
            {
                RemoteStub aNameServerStub = new RemoteStub( endPoint.address,
                                                            endPoint.port,
                                                            endPoint.port,
                                                            endPoint.address );
                nameServerStub = aNameServerStub;
                break; // stop at first one we can connect to
            }
            catch( IOException ioe )
            {
                String s = "Failed to connect to NamingService - "+
                        endPoint + " trying next one.";
                System.out.println(s);
            }
        }
        
        // If no nameingService - complain
        if( nameServerStub == null )
        {
            String s = "Unable to connect to NamingService.";
            throw new IOException( s );
        }
        
        return nameServerStub;
    }
    
    
    public boolean createCalendar(User user) throws IOException
    {
        RemoteStub nameServerStub = this.getNamingServiceStub();
        
        nameServerStub.register(Topic.RESOURCE_RESPONSE, ResourceResponse.class);
        
        /*
         * Figure out from namingServer where user name maps to
         */
        ResourceRequest request = new ResourceRequest();
        request.resource = user.getName();
        
        Message message = nameServerStub.execute(Topic.RESOURCE_REQUEST, request);
        ResourceResponse response = (ResourceResponse)message;
        
        EndPoint primary = response.host.primaryHost;
        EndPoint replica = response.host.replicaHost;
        
        // Try primary first - then secondary
        RemoteStub stub = this.getRemoteStub(primary);
        if( stub == null )
        {
            // Try replica
            stub = this.getRemoteStub(replica);
        }
        
        if( stub == null )
        {
            String s = "Unable to connect to NamingService for "+user.getName();
            System.out.println( s );
            return false;
        }
        else
        {
            stub.register(calendar.Topic.CREATE_CALENDAR_RESPONSE, Result.class);
            Message m = stub.execute(calendar.Topic.CREATE_CALENDAR_REQUEST, user);
            Result result = (Result)m;
            System.out.println( result.result );
            return result.result;
        }
    }

    public boolean connectCalendar(User user) throws IOException
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean disconnectCalendar(User user) throws IOException
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public ArrayList list() throws IOException
    {
        ArrayList<User> allUsers = new ArrayList<User>();
        /*
         * Connect to all nameServers and get all CalendarManagers' lists
         */
        RemoteStub nameServerStub = this.getNamingServiceStub();
        
        nameServerStub.register(Topic.LIST_MANAGER_RESPONSE, ListManagerResponse.class);
                
                
        // If here - we connected - we can break at end
        ListManagerRequest request = new ListManagerRequest();
        Message message = nameServerStub.execute(Topic.LIST_MANAGER_REQUEST, request);
        ListManagerResponse response = (ListManagerResponse)message;

        ArrayList<Host> calendarManagers = response.hosts;
        
        
        /*
         * Now go through list - if we fail to connect try replica
         * and if that fails - continue with the rest
         */
        for (int i = 0; i < calendarManagers.size(); i++)
        {
            Host host = calendarManagers.get(i);
            EndPoint primary = host.primaryHost;
            EndPoint replica = host.replicaHost;
            
            // Try primary
            ArrayList<User> users = this.getUsers(primary);
            if( users == null )
            {
                users = this.getUsers(replica);
            }
            
            if( users != null )
            {
                allUsers.addAll(users);
            }
            else
            {
                System.out.println("Unable to get primary/replica calendars for "+host);
            }
        }
        
        return allUsers;
    }

    public void isAlive() throws IOException
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    /**
     * Gets list of user objects from calendarManager.
     * @param mgrEndPoint
     * @return list or null if a problem
     */
    private ArrayList<User> getUsers( EndPoint mgrEndPoint )
    {
        ArrayList<User> users = null;
        try
        {
            RemoteStub calMgrStub = new RemoteStub( mgrEndPoint.address,
                                                    mgrEndPoint.port,
                                                    mgrEndPoint.port,
                                                    mgrEndPoint.address );
            
            calMgrStub.register(calendar.Topic.LIST_RESPONSE, ListResponse.class);
                    
            ListRequest request = new ListRequest();
            Message message = calMgrStub.execute(calendar.Topic.LIST_REQUEST, request);
            ListResponse response = (ListResponse)message;
            users = response.getUsers();
        }
        catch(IOException ioe)
        {
            // ignore
        }
        return users;
    }
    
    
    public static void main( String[] args ) throws IOException, InterruptedException
    {
        if( args.length != 1 )
        {
            String s = "Usage: ClientFrontEnd namingHost";
            System.out.println( s );
            return;
        }
        
        String namingHost = args[0];
        
        ClientFrontEnd frontEnd = new ClientFrontEnd( namingHost, NamingService.WELL_KNOWN_NAMING_PORT );
        ArrayList users = frontEnd.list();
        for (int i = 0; i < users.size(); i++)
        {
            User user = (User)users.get(i);
            System.out.println(user);
        }
        
        User user = new User();
        user.setName("Alice");
        user.setPort(9001);
        frontEnd.createCalendar(user);
    }

    
}